package com.lancoo.ccas53.arrangecourse.coursescheduling.campusscheduling;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.lancoo.ccas53.arrangecourse.common.BinaryUtil;
import com.lancoo.ccas53.arrangecourse.common.ClassHourUtil;
import com.lancoo.ccas53.arrangecourse.coursescheduling.allocatetimeslotroom.AllocateNoClashTimeslotRoom;
import com.lancoo.ccas53.arrangecourse.coursescheduling.allocatetimeslotroom.AllocateTimeslotRoom;
import com.lancoo.ccas53.arrangecourse.coursescheduling.assignlist.SpecifiedTimelotRoom;
import com.lancoo.ccas53.arrangecourse.dataprocess.GenerateMergeClassGroup;
import com.lancoo.ccas53.arrangecourse.dataprocess.GenerateSchedulingData;
import com.lancoo.ccas53.arrangecourse.dataprocess.GenerateSplitClassGroup;
import com.lancoo.ccas53.arrangecourse.entities.ClassHour;
import com.lancoo.ccas53.arrangecourse.entities.CourseUnit;
import com.lancoo.ccas53.arrangecourse.entities.TeachingClassUnit;
import com.lancoo.ccas53.arrangecourse.entities.TimeslotRoom;
import com.lancoo.ccas53.arrangecourse.rulecheck.RuleInit;
import com.lancoo.ccas53.entity.SubClassGroup;
import com.lancoo.ccas53.pojo.dto.ArrangeCourseRuleDto;
import com.lancoo.ccas53.pojo.dto.ArrangeTeacherRuleDto;
import com.lancoo.ccas53.pojo.dto.ArrangeTeachingClassDto;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @Author: liufeng
 * @CreateTime: 2024-09-11  19:12
 * @Description:
 */
@Slf4j
public class MergeTeachingClassWeekScheduling extends GenerateSchedulingData {
    //定义分配教室课时分组的对象
    private SpecifiedTimelotRoom specifiedTimelotRoom;

    private GenerateMergeClassGroup mergeClassGroup;

    public MergeTeachingClassWeekScheduling(RuleInit ruleInit) {
        //初始化，按教室类型，处理教室课时分组信息列表的对象
        //初始化排课的对象
        if (specifiedTimelotRoom == null) {
            //初始化排课使用的教室课时分配的类，分配无冲突的教室课时分组信息；
            AllocateTimeslotRoom allocateNoClashTimeslotRoom = new AllocateNoClashTimeslotRoom();
            //初始化拆班排课，需要满足的规则
            specifiedTimelotRoom = new SpecifiedTimelotRoom(ruleInit.getNoClashBaseRule(), allocateNoClashTimeslotRoom);
        }

        if (mergeClassGroup == null) {
            //初始化拆班分组教学班对象
            mergeClassGroup = new GenerateMergeClassGroup();
        }
    }

    public LinkedList<TeachingClassUnit> start(Integer taskId,
                                               List<ArrangeTeachingClassDto> teachingClassList,
                                               LinkedList<TimeslotRoom> timeslotRoomList,
                                               ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                               List<ArrangeTeacherRuleDto> teacherRuleList,
                                               List<ArrangeCourseRuleDto> courseRuleList) {
        //教学班列表不为空
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            if (timeslotRoomList == null || timeslotRoomList.isEmpty()) {
                log.error("mergeTeachingClassWeekScheduling--start:没有用于排课的教室课时分组信息~！！");
                return null;
            }

            //筛选教学班列表中，需要拆班的教学班信息；
            List<ArrangeTeachingClassDto> mergeTeachingClassList = findMergeTeachingClass(teachingClassList);
            mergeTeachingClassList = markFixTimeslot(mergeTeachingClassList, teacherRuleList, courseRuleList);

            if (mergeTeachingClassList != null && !mergeTeachingClassList.isEmpty()) {
                log.info("mergeTeachingClassWeekScheduling--start:需要合并授课方式的教学班的数量为：" +
                        (mergeTeachingClassList != null ? mergeTeachingClassList.size() : 0));
                //记录，锁定的教室课时的数量，生成教学班的时候，用来作为教学班排课单元的起始值
                //这样，最后生成的排课单元的id，是连续的
                Integer size = ClassHourUtil.getMapElementSize(classHourMap);

                //将教学班信息，转换成排课的教学班单元
                LinkedList<TeachingClassUnit> teachingClassUnitList = convertToTeachingClassUnit(taskId, mergeTeachingClassList, size);
                log.info("mergeTeachingClassWeekScheduling--start:转换为教学班排课单元的数量为：" +
                        (teachingClassUnitList != null ? teachingClassUnitList.size() : 0));

                //将拆班教学班进行分组
                teachingClassUnitList = mergeClassGroup.start(teachingClassUnitList);

                //将教学班排课单元，按照分组标识，生成map，key为分组标识(UUID字符串)
                //相同分组标识的教学班，需要分配相同课时的教室
                Map<String, LinkedList<TeachingClassUnit>> groupedUnitMap = teachingClassUnitList.stream()
                        .collect(Collectors.groupingBy(TeachingClassUnit::getSplitClassGroupId, Collectors.toCollection(LinkedList::new)));

                //遍历待排教学班排课单元map
                for (Map.Entry<String, LinkedList<TeachingClassUnit>> entry : groupedUnitMap.entrySet()) {
                    //找出当前待分配的教学班排课单元列表信息，目前一个分组对应两个教学班排课单元
                    LinkedList<TeachingClassUnit> allocatedUnitList = entry.getValue();
                    //开始拆班教学班的排课
                    allocatedUnitList = startMergeScheduling(allocatedUnitList, timeslotRoomList, classHourMap);
                    //如果教室课时分组信息，分配成功
                    if (allocatedUnitList != null
                            && !allocatedUnitList.isEmpty()) {
                        //添加已排好的教室课时分组信息，到ClassHour
                        classHourMap = GenerateClashesClassHour(allocatedUnitList, classHourMap);
                        //从可分配的列表中，删除已分配的教室课时分组信息
                        removeTimeslotRoomList(timeslotRoomList, getUnitTimeslotList(allocatedUnitList));
                        //将当前教学班排课单元列表，从待分配的列表中移除
                        teachingClassUnitList.removeAll(allocatedUnitList);
                    }
                }

                log.info("mergeTeachingClassWeekScheduling--start:剩余未分配的，要合并授课方式的排课单元数量为：" +
                        (teachingClassUnitList != null ? teachingClassUnitList.size() : 0));

                groupedUnitMap.clear();
                groupedUnitMap = null;
                return teachingClassUnitList;
            }
            log.info("mergeTeachingClassWeekScheduling--start:没有需要拆分的教学班信息~！！");
        }
        log.info("mergeTeachingClassWeekScheduling--start:没有需要处理的教学班信息~！！");

        return null;
    }

    private List<ArrangeTeachingClassDto> markFixTimeslot(List<ArrangeTeachingClassDto> teachingClassList,
                                                          List<ArrangeTeacherRuleDto> teacherRuleList,
                                                          List<ArrangeCourseRuleDto> courseRuleList) {
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            if (teacherRuleList != null && !teacherRuleList.isEmpty()) {
                log.info("mergeTeachingClassWeekScheduling--markFixTimeslot:获取教师优先排规则{}条", teacherRuleList.size());
                //将教师优先排设置的课时信息，添加到教学班列表
                teachingClassList = markTeacherFixTimeslot(teachingClassList, teacherRuleList);
            }

            if (courseRuleList != null && !courseRuleList.isEmpty()) {
                log.info("mergeTeachingClassWeekScheduling--markFixTimeslot:获取课程优先排规则{}条", courseRuleList.size());
                //将课程优先排设置的课时信息，添加到教学班列表
                teachingClassList = markCourseFixTimeslot(teachingClassList, courseRuleList);
            }
        }

        return teachingClassList;
    }

    /*
    ===========================================标记优先排====================================================
     */
    private List<ArrangeTeachingClassDto> markTeacherFixTimeslot(List<ArrangeTeachingClassDto> teachingClassList,
                                                                 List<ArrangeTeacherRuleDto> teacherRuleList) {
        if (teachingClassList != null && !teachingClassList.isEmpty()
                && teacherRuleList != null && !teacherRuleList.isEmpty()) {
            //筛选出教师尽量排的规则
            List<ArrangeTeacherRuleDto> teacherFixRuleList = teacherRuleList.stream()
                    .filter(rule -> rule.getFlag().equals(2))
                    .collect(Collectors.toList());

            log.info("FixScheduling--markTeacherFixTimeslot:获取教师优先排规则{}条", teacherFixRuleList.size());
            if (teacherFixRuleList != null && !teacherFixRuleList.isEmpty()) {
                for (ArrangeTeacherRuleDto teacherRule : teacherFixRuleList) {
                    //定义列表遍历的迭代器
                    Iterator iterator = teachingClassList.iterator();
                    while (iterator.hasNext()) {
                        ArrangeTeachingClassDto teachingClass = (ArrangeTeachingClassDto) iterator.next();
                        //判断教学班教师中，是否包含该教师的固定排信息
                        if (isTeacherIdExist(teachingClass.getTeacherIds(), teacherRule.getTeacherId() + "")) {

                            //获取课时编码，0102
                            String timeslotCode = teacherRule.getTimeCode();
                            //更新教学班信息中的，尽量排字符串属列表性
                            teachingClass.setFixTimeslotCodes(updateFixTimeslotCodes(teachingClass.getFixTimeslotCodes(),
                                    timeslotCode));
                        }
                    }
                }
            } else {
                log.info("FixScheduling--markTeacherFixTimeslot:没有需要标记的教师尽量排规则");
            }
        }

        return teachingClassList;
    }

    private List<ArrangeTeachingClassDto> markCourseFixTimeslot(List<ArrangeTeachingClassDto> teachingClassList,
                                                                List<ArrangeCourseRuleDto> courseRuleList) {
        //将课程优先排的课时，标记到教学班
        if (teachingClassList != null && !teachingClassList.isEmpty()
                && courseRuleList != null && !courseRuleList.isEmpty()) {

            //筛选出课程尽量排的规则
            List<ArrangeCourseRuleDto> courseFixList = courseRuleList.stream()
                    .filter(rule -> rule.getFlag().equals(2))
                    .collect(Collectors.toList());
            log.info("FixScheduling--markCourseFixTimeslot:获取课程优先排规则{}条", courseFixList.size());

            if (courseFixList != null && !courseFixList.isEmpty()) {
                //定义遍历课程优先排列表
                for (ArrangeCourseRuleDto courseRule : courseRuleList) {
                    for (ArrangeTeachingClassDto teachingClass : teachingClassList) {
                        //通过教学班id，进行匹配
                        if (courseRule.getTeachingClassId().equals(teachingClass.getTeachingClassId())
                                && Objects.equals(courseRule.getHourType(), teachingClass.getHourType())) {
                            //将数据添加到优先排的教学班列表
                            //获取课时编码，0102
                            String timeslotCode = courseRule.getTimeCode();
                            //更新教学班信息中的，尽量排字符串属列表性
                            teachingClass.setFixTimeslotCodes(updateFixTimeslotCodes(teachingClass.getFixTimeslotCodes(),
                                    timeslotCode));
                        }
                    }
                }
            } else {
                log.info("FixScheduling--markCourseFixTimeslot:没有需要标记的课程尽量排规则");
            }
        }
        return teachingClassList;
    }

    private List<String> updateFixTimeslotCodes(List<String> fixTimeslotCodes,
                                                String timeslotCode) {

        if (fixTimeslotCodes == null) {
            //创建列表
            fixTimeslotCodes = new ArrayList<>();
        }
        //如果不存在重复的，则将其加入列表
        if (!fixTimeslotCodes.contains(timeslotCode)) {
            //将这条规则加入列表
            fixTimeslotCodes.add(timeslotCode);
        }

        return fixTimeslotCodes;
    }
    /*
    ===========================================================================================================
     */

    private LinkedList<TimeslotRoom> getUnitTimeslotList(LinkedList<TeachingClassUnit> allocatedUnitList) {
        //定义返回的列表信息；
        LinkedList<TimeslotRoom> retList = new LinkedList<>();
        if (allocatedUnitList != null && !allocatedUnitList.isEmpty()) {
            for (TeachingClassUnit teachingClassUnit : allocatedUnitList) {
                //获取教学班排课单元分配的教室课时分组信息列表
                LinkedList<TimeslotRoom> timeslotRoomList = teachingClassUnit.getTimeslotRoomList();

                if (timeslotRoomList != null && !timeslotRoomList.isEmpty()) {
                    //分配给返回列表，进行返回
                    retList.addAll(timeslotRoomList);
                }
            }
        }
        return retList;
    }

    private ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> GenerateClashesClassHour(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                                                                ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        if (teachingClassUnitList == null || teachingClassUnitList.isEmpty()) {
            log.error("mergeTeachingClassWeekScheduling--GenerateClashesClassHour:待转换的教学班排课单元列表为空~！！");
            return classHourMap;
        }
        //生成教学班排课单元信息，并添加到map
        for (TeachingClassUnit teachingClassUnit : teachingClassUnitList) {
            for (TimeslotRoom timeslotRoom : teachingClassUnit.getTimeslotRoomList()) {
                //生成排课单元信息
                ClassHour classHour = new ClassHour();
                //为排课单元，添加教学班数据
                classHour.setCourseUnit(new CourseUnit(teachingClassUnit));
                //为排课单元，添加教室课时分组数据
                classHour.setTimeslotRoom(new TimeslotRoom(timeslotRoom));
                //获取分配的教室课时分组的，课时编码信息，如：0101代表周一第一节课，作为key，进行返回
                String key = timeslotRoom.getTimeslotCode();
                //将获取到的classHour存入map
                if (classHourMap.get(key) == null) {
                    //创建排课单元列表信息
                    CopyOnWriteArrayList<ClassHour> classHourList = new CopyOnWriteArrayList<>();
                    //将生成的教学班排课单元，添加到列表
                    classHourList.add(classHour);
                    //将列表添加到map中
                    classHourMap.put(key, classHourList);
                } else {
                    //直接将排课单元信息，添加到map里指定的列表中
                    classHourMap.get(key).add(classHour);
                }
            }
        }

        return classHourMap;
    }

    private LinkedList<TeachingClassUnit> startMergeScheduling(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                               LinkedList<TimeslotRoom> timeslotRoomList,
                                                               ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        Integer roomType = teachingClassUnitList.get(0).getRoomType();
        //将教室课时分组信息列表，以教室id为key，格式化保存为map
        Map<Long, LinkedList<TimeslotRoom>> roomIdRoomMap = timeslotRoomList.stream()
                .filter(o -> o.getRoomType().equals(roomType))
                .collect(Collectors.groupingBy(TimeslotRoom::getRoomId, Collectors.toCollection(LinkedList::new)));
        //定义待分配的List，用来保存筛选后的教室课时分组信息
        LinkedList<TimeslotRoom> allocatedList = new LinkedList<>();
        //筛选教学班排课单元，指定的教室对应的教室课时分组信息；
        for (TeachingClassUnit teachingClassUnit : teachingClassUnitList) {
            //获取教学班排课单元指定的教室id，多个以逗号分隔
            Long roomId = teachingClassUnit.getRoomId();
            if (roomId != null) {
                LinkedList<TimeslotRoom> roomList = roomIdRoomMap.get(roomId);
                //如果某个教学班指定的教室，没有可分配的教室课时分组信息
                if (roomList == null) {
                    log.error("#SplitTeachingClassScheduling.startSplitScheduling#:拆班教学班指定的ID为：" + roomId
                            + "的教室，没有可分配的教室课时分组信息~！！");
                    //清空map
                    roomIdRoomMap.clear();
                    roomIdRoomMap = null;
                    //直接返回空，说明此时这个教室不够分了，这两个拆分的教学班，
                    //只能在自动排课的时候，分配在类型相同的其他教室了
                    return null;
                }

                //将拆班课程，指定的教室的教室课时分组信息，进行添加
                allocatedList.addAll(roomList);
                break;
            }

        }
        if (CollUtil.isEmpty(allocatedList)) {
            allocatedList.addAll(timeslotRoomList.stream()
                    .filter(o -> o.getRoomType().equals(roomType)).collect(Collectors.toCollection(LinkedList::new)));
        }
        //周次合并之后能分配的
        List<String> weeks = teachingClassUnitList.stream().map(TeachingClassUnit::getClassWeeks).collect(Collectors.toList());
        String week = BinaryUtil.binaryORMore(weeks);
        allocatedList = allocatedList.stream()
                .filter(o -> BinaryUtil.binaryAndResultEqual(o.getWeeks(), week))
                .collect(Collectors.toCollection(LinkedList::new));

        //将待分配的教室课时分组信息列表，按照教室id，生成map
        Map<Long, LinkedList<TimeslotRoom>> timeslotIdMap = allocatedList.stream()
                .collect(Collectors.groupingBy(TimeslotRoom::getRoomId, Collectors.toCollection(LinkedList::new)));


        //按照课时编码进行遍历，同时为教学班排课单元，指定可以分配的教室课时分组信息
        for (Map.Entry<Long, LinkedList<TimeslotRoom>> entry : timeslotIdMap.entrySet()) {
            //获取指定课时的，教室课时分组列表信息
            LinkedList<TimeslotRoom> timeslotCodeList = entry.getValue();

            //按照课时进行教室课时分组信息的指定分配
            LinkedList<TeachingClassUnit> teachingClassUnits = startAllocate(teachingClassUnitList, timeslotCodeList, classHourMap);
            //如果教师课时分组信息不够分配，将返回空
            if (teachingClassUnits == null
                    || teachingClassUnits.isEmpty()) {
                //此时直接向下一个课时进行遍历即可
                continue;
            }
            roomIdRoomMap.clear();
            roomIdRoomMap = null;

            timeslotIdMap.clear();
            timeslotIdMap = null;

            //如果都分配成功了，直接返回
            return teachingClassUnitList;

        }
        roomIdRoomMap.clear();
        roomIdRoomMap = null;

        timeslotIdMap.clear();
        timeslotIdMap = null;
        //遍历完所有的，都没有分配成功，那么返回空
        return null;
    }

    private LinkedList<TeachingClassUnit> startAllocate(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                        LinkedList<TimeslotRoom> timeslotRoomList,
                                                        ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        LinkedList<TeachingClassUnit> retList = new LinkedList<>();
        //当列表不为空，且指定课时编码，所对应的教室课时分组的数量与教学班排课单元列表的数量相等才分配
        if (timeslotRoomList != null && !timeslotRoomList.isEmpty()) {
            //待分配的教室课时分组对象，需要转换成列表进行处理
            for (TeachingClassUnit teachingClassUnit : teachingClassUnitList) {
                //为指定排课教学班单元，分配教室课时分组信息；
                teachingClassUnit = specifiedTimelotRoom.startAllocation(teachingClassUnit, timeslotRoomList, classHourMap);

                //如果其中一个匹配不成功，直接跳出匹配循环
                if (teachingClassUnit.getTimeslotRoomList() == null) {
                    //分配不成功，将返回值列表置空~
                    retList = null;
                    //之前已经分配的，需要置空，进行回滚
                    teachingClassUnitList = rollbackAllocated(teachingClassUnitList, timeslotRoomList);

                    return null;
                }

                //将分配的分组信息，加入列表
                retList.add(teachingClassUnit);
            }

        }

        return retList;
    }

    private LinkedList<TeachingClassUnit> rollbackAllocated(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                            LinkedList<TimeslotRoom> timeslotRoomList) {
        for (TeachingClassUnit teachingClassUnit : teachingClassUnitList) {
            //获取待分配的教室课时分组信息列表
            LinkedList<TimeslotRoom> allocatedRoomList = teachingClassUnit.getTimeslotRoomList();
            //如果分配失败了，将已分配的列表设为空
            if (allocatedRoomList != null && !allocatedRoomList.isEmpty()) {
                for (TimeslotRoom backTimeslotRoom : allocatedRoomList) {
                    //如果是完全匹配
                    if (backTimeslotRoom.getIsRemove()) {
                        //将之前分配的教室课时分组信息进行归还，如果不为空
                        backTimeslotRoom.setIsRemove(false);
                        timeslotRoomList.add(backTimeslotRoom);
                    } else {
                        String roomWeek = backTimeslotRoom.getWeeks();
                        String classWeek = teachingClassUnit.getClassWeeks();
                        for (TimeslotRoom timeslotRoom : timeslotRoomList) {
                            if (timeslotRoom.getTimeslotRoomId() == backTimeslotRoom.getTimeslotRoomId()) {
                                timeslotRoom.setWeeks(BinaryUtil.binaryOR(roomWeek, classWeek));
                            }
                        }
                    }
                    //将分配的列表置空
                    allocatedRoomList.clear();
                    //是否空间
                    allocatedRoomList = null;
                    //将以前分配的去掉，并设为空，下轮重新分配新的课时
                    teachingClassUnit.setTimeslotRoomList(allocatedRoomList);
                }

            }

        }
        return teachingClassUnitList;
    }


    private List<ArrangeTeachingClassDto> findMergeTeachingClass(List<ArrangeTeachingClassDto> teachingClassList) {
        List<ArrangeTeachingClassDto> mergeTeachingClassList = new ArrayList<>();
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            Map<Long, List<ArrangeTeachingClassDto>> teachingClassMap = teachingClassList.stream()
                    .collect(Collectors.groupingBy(ArrangeTeachingClassDto::getTeachingClassId));
            for (List<ArrangeTeachingClassDto> value : teachingClassMap.values()) {
                if (value != null && value.size() > 1) {
                    ArrangeTeachingClassDto item = value.get(0);
                    if (value.stream().allMatch(o ->
                            o.getWeekNum().equals(item.getWeekNum())
                                    && o.getConnectSection().equals(item.getConnectSection())
                                    && o.getConnectNumber().equals(item.getConnectNumber())
                                    && o.getRoomType().equals(item.getRoomType())
                    )
                            && value.stream().filter(o -> o.getRoomId() != null)
                            .map(ArrangeTeachingClassDto::getRoomId).count() <= 1
                    ) {
                        List<String> weeks = value.stream().map(ArrangeTeachingClassDto::getWeek).collect(Collectors.toList());
                        if (!BinaryUtil.binaryAndMore(weeks).contains("1")) {
                            mergeTeachingClassList.addAll(value);
                        }
                    }
                }
            }

            teachingClassList.removeAll(mergeTeachingClassList);
        }

        return mergeTeachingClassList;
    }
}
